package com.ml4ai.backend.utils;

import lombok.SneakyThrows;
import org.springframework.beans.BeanUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Created by leecheng on 2017/11/14.
 */
public class Bean2Bean {

    private List<PropertyMapper> mappers = new ArrayList<>();

    private List<String> excludeProps = new ArrayList<>();

    public Bean2Bean(PropertyMapper... mappers) {
        this.mappers.addAll(Arrays.asList(mappers));
    }

    public Bean2Bean addExcludeProp(String... props) {
        this.excludeProps.addAll(Arrays.asList(props));
        return this;
    }

    public Bean2Bean addPropMapper(PropertyMapper... propertyMapper) {
        this.mappers.addAll(Arrays.asList(propertyMapper));
        return this;
    }

    @SneakyThrows
    public void copyProperties(Object src, Object dest) {
        copyProperties(src, dest, false);
    }

    @SneakyThrows
    public void copyProperties(Object src, Object dest, boolean excludeNull) {
        List<String> nullValueProperties = new LinkedList<>();
        if (excludeNull) {
            Class<?> clz = src.getClass();
            List<String> fields = new ArrayList<>();
            while (clz != Object.class) {
                Field[] fs = clz.getDeclaredFields();
                if (fs != null) {
                    for (Field field : fs) {
                        if (!field.isAccessible()) field.setAccessible(true);
                        if (field.get(src) == null) fields.add(field.getName());
                    }
                }
                clz = clz.getSuperclass();
            }
            nullValueProperties.addAll(fields);
        }

        List<String> excludes = new ArrayList<>();
        excludes.addAll(mappers.stream().map(PropertyMapper::getSource).collect(Collectors.toList()));
        excludes.addAll(excludeProps);
        excludes.addAll(nullValueProperties);
        BeanUtils.copyProperties(src, dest, excludes.toArray(new String[excludes.size()]));
        for (PropertyMapper propertyMapper : mappers) {
            String sPropName = propertyMapper.getSource();
            String tPropName = propertyMapper.getTarget();
            Object val = getDeepValue(src, sPropName);
            if (excludeNull && val == null) {
                continue;
            }
            setDeepValue(dest, tPropName, propertyMapper.map(val));
        }
    }

    public static <T> T getDeepValue(Object bean, String prop) {
        String[] propArr = prop.split("\\.");
        Object currentVal = bean;
        for (int i = 0; i < propArr.length; i++) {
            String currentProp = propArr[i];
            if (currentVal == null) {
                return null;
            } else {
                currentVal = getField(currentVal, currentProp);
            }
        }
        return (T) currentVal;
    }

    @SneakyThrows
    public static boolean setDeepValue(Object bean, String prop, Object value) {
        String[] propArr = prop.split("\\.");
        Object currentVal = bean;
        for (int i = 0; i < propArr.length; i++) {
            String current = propArr[i];
            if (i < propArr.length - 1) {
                //处理中间属性
                Object propObject = getField(currentVal, current);
                if (propObject == null) {
                    Object val = getFieldClass(currentVal, current).newInstance();
                    setField(currentVal, current, val);
                    currentVal = val;
                } else {
                    currentVal = propObject;
                }
            } else {
                setField(currentVal, current, value);
            }
        }
        return false;
    }

    @SneakyThrows
    public static <T> T getField(Object bean, String prop) {
        if (bean == null) return null;
        Class<?> clazz = bean.getClass();
        while (clazz != Object.class) {
            String getterName = "get" + prop.substring(0, 1).toUpperCase() + prop.substring(1);
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (method.getName().equalsIgnoreCase(getterName) && method.getParameterCount() == 0) {
                    return (T) method.invoke(bean);
                }
            }
            clazz = clazz.getSuperclass();
        }
        throw new IllegalStateException("未发现属性：".concat(prop));
    }

    @SneakyThrows
    public static void setField(Object bean, String prop, Object object) {
        Class<?> clazz = bean.getClass();
        while (clazz != Object.class) {
            String setterName = "set" + prop.substring(0, 1).toUpperCase() + prop.substring(1);
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (method.getName().equalsIgnoreCase(setterName) && method.getParameterCount() == 1) {
                    method.invoke(bean, new Object[]{object});
                }
            }
            clazz = clazz.getSuperclass();
        }
    }

    public static Class<?> getFieldClass(Object bean, String prop) {
        Class<?> clazz = bean.getClass();
        while (clazz != Object.class) {
            String getterName = "get" + prop.substring(0, 1).toUpperCase() + prop.substring(1);
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (method.getName().equalsIgnoreCase(getterName) && method.getParameterCount() == 0) {
                    return method.getReturnType();
                }
            }
            clazz = clazz.getSuperclass();
        }
        throw new IllegalStateException("无法知道类型");
    }

    public static <T> T getFirstNoNullVal(Object bean, String... props) {
        if (props.length == 0) {
            return null;
        } else {
            for (int i = 0; i < props.length; i++) {
                T target = getDeepValue(bean, props[i]);
                if (target != null) {
                    return target;
                }
            }
            return null;
        }
    }
}
